package com.pokermind.operator;

import com.pokermind.common.context.RequestContext;
import com.pokermind.common.log.PlainLog;
import com.pokermind.common.log.TimeCostLog;
import com.pokermind.common.util.JsonUtil;
import com.pokermind.model.dto.*;
import com.pokermind.model.po.PokerHand;
import com.pokermind.model.po.PokerHandAction;
import com.pokermind.repository.dao.PokerHandDao;
import com.pokermind.repository.dao.PokerHandActionDao;
import com.pokermind.repository.dao.PokerDecisionSnapshotDao;
import com.pokermind.operator.ai.PokerMathCalculator;
import com.pokermind.operator.engine.MathFallbackDecision;
// import com.pokermind.operator.ai.PokerAIService; // 暂时禁用AI服务
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import jakarta.annotation.PostConstruct;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

/**
 * AI决策分析业务操作类 实现高级扑克AI分析功能
 *
 * @author PokerMind Team
 */
@Service
@Transactional
public class AIAnalysisOperator {

    private static final Logger logger = LoggerFactory.getLogger(AIAnalysisOperator.class);

    @Autowired
    private PokerHandDao pokerHandDao;

    @Autowired
    private PokerHandActionDao pokerHandActionDao;

    @Autowired
    private PokerMathCalculator mathCalculator;

    @Autowired
    private com.pokermind.operator.service.PokerAIService pokerAIService;

    @Autowired(required = false)
    private com.pokermind.operator.service.SiliconFlowHttpClient siliconFlowClient;

    @Autowired
    private com.pokermind.operator.engine.MathFallbackEngine mathFallbackEngine;

    @Value("${pokermind.ai.timeout-ms}")
    private int aiTimeoutMs;

    @Value("${pokermind.ai.enable-math-fallback:true}")
    private boolean enableMathFallback;
    
    @Value("${pokermind.ai.siliconflow.enabled:false}")
    private boolean siliconFlowEnabled;

    @PostConstruct
    public void init() {
        logger.info("========================================");
        logger.info("AIAnalysisOperator Configuration:");
        logger.info("  aiTimeoutMs: {} ms", aiTimeoutMs);
        logger.info("  enableMathFallback: {}", enableMathFallback);
        logger.info("  siliconFlowEnabled: {}", siliconFlowEnabled);
        if (siliconFlowEnabled && siliconFlowClient != null) {
            logger.info("  ✓ SiliconFlow HTTP Client available");
        }
        logger.info("========================================");
    }

    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 5.3.1 请求AI分析 POST /ai/analyze
     * V4.9: 永远返回 GTO + AI 两种结果，用 recommended_source 标识
     */
    public AIAnalysisResponse performAIAnalysis(AIAnalysisRequest request) {
        long startTime = System.currentTimeMillis();
        PlainLog log = new TimeCostLog("op", "ai_analysis")
            .put("rqid", RequestContext.getRequestId())
            .put("tableId", request.getTableId())
            .put("handId", request.getHandId())
            .put("playerId", request.getPlayerId());
        
        // 添加请求内容到上下文（便于错误日志打印）
        String reqContent = RequestContext.getRequestContent();
        if (reqContent != null) {
            log.put("reqContent", reqContent);
        }
            
        try {
            // 1. V4.9: 验证请求（hole_cards 必需）
            validateAnalysisRequestV49(request);
            
            // 2. 查询数据
            PokerHand hand = findHandByTableAndHandId(request.getTableId(), request.getHandId());
            if (hand == null) {
                throw new IllegalArgumentException("Hand not found: " + request.getHandId() + 
                    " at table: " + request.getTableId());
            }
            List<PokerHandAction> actions = getHandActions(hand.getId());
            
            String playerId = request.getPlayerId();
            List<Integer> heroCards = request.getHeroCards();
            String playerPos = extractPlayerPosition(hand, playerId);
            
            // 转换卡牌格式：数字 → 字符串 (如 50 → "Qd")
            List<String> holeCards = convertCardsToStrings(heroCards);
            
            log.put("tableHandId", request.getTableId() + "-" + request.getHandId())
                .put("street", hand.getCurrentStreet())
                .put("playerPos", playerPos)
                .put("holeCards", holeCards);  // 只显示转换后的底牌
            
            // 只在有行动时显示行动数量
            if (actions != null && !actions.isEmpty()) {
                log.put("actionsCount", actions.size());
            }
            
            // 3. 数学计算（蒙特卡洛）
            MathAnalysisResult mathResult = performMathematicalAnalysis(hand, actions, holeCards, null, log);
            
            // 只显示有意义的数学结果
            if (mathResult.equity > 0) {
                log.put("equity", String.format("%.2f", mathResult.equity));
            }
            if (hand.getCurrentPot() != null && hand.getCurrentPot().compareTo(BigDecimal.ZERO) > 0) {
                log.put("pot", hand.getCurrentPot());
            }
            if (mathResult.expectedValue != 0.0 && !Double.isNaN(mathResult.expectedValue)) {
                log.put("ev", String.format("%.0f", mathResult.expectedValue));
            }
            
            // 4. GTO 分析（必定成功）- 只返回概率分布
            Map<String, Double> gtoFrequencies = calculateGTOFrequencies(mathResult.equity, playerPos);
            String gtoOptimalAction = getOptimalAction(gtoFrequencies);
            log.put("gtoAction", gtoOptimalAction);
            
            // 5. AI 分析（可能失败）
            AIAnalysisResponse.AIDecision aiDecision = null;
            String aiStatus = "success";
            String recommendedSource = "ai";  // 默认推荐 AI
            
            try {
                long aiStart = System.currentTimeMillis();
                PokerAIRequest aiRequest = buildProfessionalPokerInput(hand, actions, holeCards, playerPos, mathResult, playerId, request);
                PokerAIResponse aiResponse;
                
                if (siliconFlowEnabled && siliconFlowClient != null) {
                    // V4.10: 传递GTO频率到AI Prompt
                    aiResponse = callSiliconFlowAI(aiRequest, gtoFrequencies);
                    log.put("aiProvider", "siliconflow");
                } else {
                    aiResponse = CompletableFuture.supplyAsync(() -> 
                        pokerAIService.analyzePokerDecision(aiRequest))
                        .get(aiTimeoutMs, TimeUnit.MILLISECONDS);
                    log.put("aiProvider", "qwen");
                }
                
                aiDecision = convertToAIDecision(aiResponse, mathResult, gtoFrequencies);
                long aiTime = System.currentTimeMillis() - aiStart;
                log.put("aiTime", aiTime + "ms")
                   .put("aiAction", aiDecision.getAction());
                   
            } catch (TimeoutException e) {
                aiStatus = "timeout";
                recommendedSource = "gto";
                log.put("aiStatus", "timeout");
                logger.warn("AI analysis timeout, using GTO fallback");
                
            } catch (Exception e) {
                aiStatus = "error";
                recommendedSource = "gto";
                log.put("aiStatus", "error").put("aiError", e.getMessage());
                logger.error("AI analysis error, using GTO fallback", e);
            }
            
            // 6. 组装响应（V4.9 极简版）
            AIAnalysisResponse response = new AIAnalysisResponse();
            
            // 设置基本信息
            response.setTableId(request.getTableId());
            response.setHandId(request.getHandId());
            response.setPlayerId(request.getPlayerId());
            
            // 获取座位号
            Integer seatNumber = extractPlayerSeatNumber(hand, playerId);
            response.setSeatNumber(seatNumber);
            
            // 设置核心字段
            response.setSuccess(true);
            response.setRecommendedSource(recommendedSource);
            response.setGto(gtoFrequencies);
            response.setAi(aiDecision);
            response.setEquity(mathResult.equity);
            response.setHandStrength(mathResult.equity);  // 手牌强度等于胜率
            
            // 7. 保存分析记录
            saveAnalysisSnapshotV49(hand, request, response);
            
            // 8. 组装最终日志（包含响应）
            log.put("recommendedSource", recommendedSource)
               .put("aiStatus", aiStatus);
            
            // 将响应作为 JSON 字符串添加到日志
            try {
                String responseJson = new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(response);
                log.put("response", responseJson);
            } catch (Exception e) {
                log.put("responseError", e.getMessage());
            }
            
            log.put("op_rslt", "success")
               .put("time", System.currentTimeMillis() - startTime + "ms");
            logger.info(log.toString());
            
            return response;
            
        } catch (IllegalArgumentException e) {
            // V4.9: 增强错误日志，包含完整请求内容
            log.put("op_rslt", "invalid_param")
               .put("error", e.getMessage())
               .put("errorDetail", "请检查请求参数，特别是 hero_cards 字段（V4.9 必需）");
            logger.warn(log.toString());
            throw e;
        } catch (Exception e) {
            log.put("op_rslt", "failed")
               .put("error", e.getMessage())
               .put("errorClass", e.getClass().getSimpleName());
            logger.error(log.toString(), e);
            throw new RuntimeException("AI analysis failed", e);
        }
    }

    /**
     * 计算数学兜底决策（延迟计算，仅在AI超时/异常时调用）
     */
    private PokerAIResponse calculateFallback(PokerAIRequest request) {
        return enableMathFallback ? mathFallbackEngine.calculate(request).toAIResponse()
            : createUltraConservativeResponse();
    }

    /**
     * 数学分析引擎 - 蒙特卡洛模拟 + 概率计算
     *
     * 核心算法： 1. 底牌解析和验证 2. 公共牌解析（支持翻牌前到河牌） 3. 活跃对手数量计算（排除已弃牌玩家） 4. 蒙特卡洛胜率模拟（10,000次） 5. 底池赔率和期望值计算
     */
    private MathAnalysisResult performMathematicalAnalysis(PokerHand hand, List<PokerHandAction> actions,
        List<String> holeCards, AIAnalysisResponse response, PlainLog log) {
        // V4.7: 支持non-hero perspective（无底牌分析）
        MathAnalysisResult result = new MathAnalysisResult();
        
        // 步骤1：检查是否有底牌
        if (holeCards == null || holeCards.isEmpty()) {
            // 无底牌模式：使用默认估算值
            log.put("analysisMode", "non-hero-perspective").put("holeCards", "not_provided");
            result.hasValidEquity = false;
            result.equity = 0.0;
            result.winRate = 0.0;
            result.expectedValue = 0.0;
            return result;
        }
        
        // 有底牌模式：验证格式
        if (holeCards.size() != 2) {
            String errorMsg = "Invalid hole cards format: " + holeCards;
            log.put("holeCardsError", errorMsg);
            throw new IllegalArgumentException(errorMsg);
        }
        // analysisMode 不需要打印（V4.9 必定是 with-hole-cards）

        // 步骤2：解析公共牌（翻牌前为空数组）
        List<String> boardCards = parseBoardCards(hand);
        // 只在有公共牌时打印
        if (boardCards != null && !boardCards.isEmpty()) {
            log.put("boardCards", boardCards);
        }

        // 步骤3：计算活跃对手数量（排除已弃牌玩家）
        int opponentCount = calculateActiveOpponents(hand, actions);
        log.put("opponents", opponentCount);

        // result已在方法开头定义（支持non-hero perspective模式）
        if (opponentCount > 0) {
            // 步骤4：蒙特卡洛胜率模拟（动态次数优化）
            int simulationCount = getOptimalSimulationCount(hand.getCurrentStreet());
            long simulationStart = System.currentTimeMillis();
            PokerMathCalculator.EquityResult equityResult =
                mathCalculator.calculateEquity(holeCards, boardCards, opponentCount, simulationCount);
            long simulationTime = System.currentTimeMillis() - simulationStart;

            // 只打印模拟时间（如果超过100ms）
            if (simulationTime > 100) {
                log.put("simTime", simulationTime + "ms");
            }

            // 步骤5：概率分析和期望值计算
            BigDecimal toCallAmount = calculateToCall(hand, actions);
            // Note: playerStack is not critical for odds analysis, using a large default value
            BigDecimal playerStack = BigDecimal.valueOf(10000); // TODO: Extract from playersInfo JSON if needed
            PokerMathCalculator.OddsAnalysis oddsAnalysis =
                mathCalculator.calculateOddsAnalysis(hand.getCurrentPot(), toCallAmount, playerStack,
                    equityResult.getEquity());

            // 只在有跟注金额时打印赔率信息
            if (toCallAmount != null && toCallAmount.compareTo(BigDecimal.ZERO) > 0) {
                log.put("toCall", toCallAmount);
                if (!Double.isNaN(oddsAnalysis.getPotOdds())) {
                    log.put("potOdds", String.format("%.2f", oddsAnalysis.getPotOdds()));
                }
            }

            // 步骤6：数据封装到响应对象
            populateMathAnalysisResponse(response, equityResult, oddsAnalysis);

            result.hasValidEquity = true;
            result.equity = equityResult.getEquity();
            result.winRate = equityResult.getWinRate();
            result.expectedValue = oddsAnalysis.getExpectedValue();
            result.potOdds = oddsAnalysis.getPotOdds();  // V4.9: 修复 pot_odds 计算

        } else {
            log.put("mathAnalysisSkipped", "no_active_opponents");
            result.hasValidEquity = false;
        }

        return result;
    }

    /**
     * 📊 数据封装 - 完成响应对象的最终组装
     * @deprecated V4.9: 已废弃，使用新的响应结构
     */
    @Deprecated
    private void finalizeAnalysisData(AIAnalysisResponse response, PokerHand hand, GameContext gameContext) {
        // V4.9: 该方法已废弃
        logger.debug("finalizeAnalysisData is deprecated in V4.9");
    }

    /**
     * 🔧 响应对象初始化
     */
    private AIAnalysisResponse initializeResponse() {
        AIAnalysisResponse response = new AIAnalysisResponse();
        // V4.9: 删除无用字段
        // response.setAnalysisId(generateAnalysisId());
        return response;
    }

    /**
     * 📈 填充数学分析结果到响应对象
     */
    private void populateMathAnalysisResponse(AIAnalysisResponse response,
        PokerMathCalculator.EquityResult equityResult, PokerMathCalculator.OddsAnalysis oddsAnalysis) {
        // 主要指标（暂时注释，待模块依赖修复后启用）
        // response.setEquity(Double.valueOf(equityResult.getEquity()));
        // response.setWinRate(Double.valueOf(equityResult.getWinRate()));
        // response.setPotOdds(Double.valueOf(oddsAnalysis.getPotOdds()));
        // response.setExpectedValue(Double.valueOf(oddsAnalysis.getExpectedValue()));

        // 详细数学分析数据（暂时注释，待模块依赖修复后启用）
        // Map<String, Object> mathAnalysis = new HashMap<>();
        // mathAnalysis.put("equity", equityResult.getEquity());
        // mathAnalysis.put("winRate", equityResult.getWinRate());
        // mathAnalysis.put("tieRate", equityResult.getTieRate());
        // mathAnalysis.put("handStrengthDistribution", equityResult.getHandStrengthDistribution());
        // mathAnalysis.put("simulationCount", equityResult.getSimulationCount());
        // mathAnalysis.put("computationTime", equityResult.getComputationTimeMs());
        // mathAnalysis.put("potOdds", oddsAnalysis.getPotOdds());
        // mathAnalysis.put("impliedOdds", oddsAnalysis.getImpliedOdds());
        // mathAnalysis.put("expectedValue", oddsAnalysis.getExpectedValue());
        // mathAnalysis.put("correctToCall", oddsAnalysis.isCorrectToCall());
        // mathAnalysis.put("actionEV", oddsAnalysis.getActionEV());
        // response.setMathAnalysis(mathAnalysis);
    }

    /**
     * 📊 数学分析结果内部类
     */
    private static class MathAnalysisResult {
        boolean hasValidEquity = false;
        double equity = 0.0;
        double winRate = 0.0;
        double expectedValue = 0.0;
        double potOdds = 0.0;  // V4.9: 修复 pot_odds 计算
    }

    /**
     * 🎯 大模型决策上下文 - 包含所有决策所需的数据
     */
    private static class GameDecisionContext {
        // 基础游戏信息
        private String street;                    // 当前街道
        private String heroPosition;             // Hero位置
        private List<String> heroCards;          // Hero底牌
        private List<String> boardCards;         // 公共牌

        // 数学分析结果
        private boolean hasValidEquity;
        private double equity;
        private double winRate;
        private double expectedValue;

        // 底池和筹码信息
        private BigDecimal currentPot;
        private BigDecimal heroStack;
        private BigDecimal potOdds;
        private double handStrength;

        // 行动历史和对手信息
        private int opponentCount;
        private List<String> recentActions;      // 最近的行动序列
        private List<String> availableActions;   // 可用行动
        private Map<String, Object> contextFields; // 其他上下文字段

        // Getters and Setters
        public String getStreet() {
            return street;
        }

        public void setStreet(String street) {
            this.street = street;
        }

        public String getHeroPosition() {
            return heroPosition;
        }

        public void setHeroPosition(String heroPosition) {
            this.heroPosition = heroPosition;
        }

        public List<String> getHeroCards() {
            return heroCards;
        }

        public void setHeroCards(List<String> heroCards) {
            this.heroCards = heroCards;
        }

        public List<String> getBoardCards() {
            return boardCards;
        }

        public void setBoardCards(List<String> boardCards) {
            this.boardCards = boardCards;
        }

        public boolean isHasValidEquity() {
            return hasValidEquity;
        }

        public void setHasValidEquity(boolean hasValidEquity) {
            this.hasValidEquity = hasValidEquity;
        }

        public double getEquity() {
            return equity;
        }

        public void setEquity(double equity) {
            this.equity = equity;
        }

        public double getWinRate() {
            return winRate;
        }

        public void setWinRate(double winRate) {
            this.winRate = winRate;
        }

        public double getExpectedValue() {
            return expectedValue;
        }

        public void setExpectedValue(double expectedValue) {
            this.expectedValue = expectedValue;
        }

        public BigDecimal getCurrentPot() {
            return currentPot;
        }

        public void setCurrentPot(BigDecimal currentPot) {
            this.currentPot = currentPot;
        }

        public BigDecimal getHeroStack() {
            return heroStack;
        }

        public void setHeroStack(BigDecimal heroStack) {
            this.heroStack = heroStack;
        }

        public BigDecimal getPotOdds() {
            return potOdds;
        }

        public void setPotOdds(BigDecimal potOdds) {
            this.potOdds = potOdds;
        }

        public double getHandStrength() {
            return handStrength;
        }

        public void setHandStrength(double handStrength) {
            this.handStrength = handStrength;
        }

        public int getOpponentCount() {
            return opponentCount;
        }

        public void setOpponentCount(int opponentCount) {
            this.opponentCount = opponentCount;
        }

        public List<String> getRecentActions() {
            return recentActions;
        }

        public void setRecentActions(List<String> recentActions) {
            this.recentActions = recentActions;
        }

        public List<String> getAvailableActions() {
            return availableActions;
        }

        public void setAvailableActions(List<String> availableActions) {
            this.availableActions = availableActions;
        }

        public Map<String, Object> getContextFields() {
            return contextFields;
        }

        public void setContextFields(Map<String, Object> contextFields) {
            this.contextFields = contextFields;
        }
    }

    // 旧的AIModelDecisionResult类已删除，现在直接使用PokerAIResponse

    /**
     * 🛠️ 构建大模型决策上下文
     */
    private GameDecisionContext buildDecisionContext(PokerHand hand, List<PokerHandAction> actions,
        GameContext gameContext, MathAnalysisResult mathResult) {
        GameDecisionContext context = new GameDecisionContext();

        // 基础游戏信息
        context.setStreet(hand.getCurrentStreet());
        context.setHeroPosition(hand.getHeroPosition());
        context.setHeroCards(parseHeroCards(hand));
        context.setBoardCards(parseBoardCards(hand));

        // 数学分析结果
        context.setHasValidEquity(mathResult.hasValidEquity);
        context.setEquity(mathResult.equity);
        context.setWinRate(mathResult.winRate);
        context.setExpectedValue(mathResult.expectedValue);

        // 底池和筹码信息
        context.setCurrentPot(gameContext.getCurrentPot());
        context.setHeroStack(gameContext.getHeroStack());
        context.setPotOdds(gameContext.getPotOdds());
        context.setHandStrength(gameContext.getHandStrength());

        // 对手和行动信息
        context.setOpponentCount(calculateActiveOpponents(hand, actions));
        context.setRecentActions(extractRecentActions(actions, 5)); // 最近5个行动
        context.setAvailableActions(determineAvailableActions(hand, gameContext));

        // 其他上下文字段
        Map<String, Object> contextFields = new HashMap<>();
        contextFields.put("handId", hand.getId());
        contextFields.put("playersDealt", hand.getPlayersDealt());
        contextFields.put("heroSeatNumber", hand.getHeroSeatNumber());
        contextFields.put("nextActionSeat", hand.getNextActionSeat());
        context.setContextFields(contextFields);
        return context;
    }

    /**
     * 🔧 提取最近的行动序列
     */
    private List<String> extractRecentActions(List<PokerHandAction> actions, int count) {
        return actions.stream().sorted((a, b) -> Long.compare(b.getId(), a.getId())) // 按ID倒序
            .limit(count).map(action -> action.getActionType() + ":" + action.getAmount()).collect(Collectors.toList());
    }

    /**
     * 🎯 确定可用行动
     */
    private List<String> determineAvailableActions(PokerHand hand, GameContext gameContext) {
        List<String> actions = new ArrayList<>();
        actions.add("fold");
        actions.add("call");

        // 根据游戏状态确定是否可以加注/下注
        if (gameContext.getHeroStack().compareTo(BigDecimal.ZERO) > 0) {
            actions.add("raise");
            actions.add("bet");

            // 全押选项
            if (gameContext.getHeroStack().compareTo(gameContext.getCurrentPot()) <= 0) {
                actions.add("allin");
            }
        }

        return actions;
    }

    // 旧的模拟AI方法已删除，现在使用专业扑克AI架构

    /**
     * 🛠️ 构建专业德州扑克决策输入 - 完整版（包含所有数学分析信息）
     * 
     * @param heroPlayerId Hero的player_id（用于正确标记isHero）
     * @param clientRequest 客户端请求（用于优先使用客户端提供的实时数据）
     */
    private PokerAIRequest buildProfessionalPokerInput(PokerHand hand, List<PokerHandAction> actions,
        List<String> holeCards, String playerPos, MathAnalysisResult mathResult, String heroPlayerId,
        AIAnalysisRequest clientRequest) {
        PokerAIRequest.PokerAIRequestBuilder builder = PokerAIRequest.builder();

        // 基本信息
        builder.street(hand.getCurrentStreet());
        builder.playerPos(playerPos != null ? playerPos : "Unknown"); // Use new field, backward compatible

        // 底池和筹码信息
        BigDecimal bigBlind = hand.getBigBlind();
        Double potBbValue = null;
        Double stackBbValue = null;

        // 设置实际金额
        if (hand.getCurrentPot() != null) {
            builder.pot(hand.getCurrentPot().doubleValue());
        }
        
        // 获取Hero当前筹码（从players_info JSON中提取）
        BigDecimal heroCurrentStack = getHeroCurrentStack(hand);
        if (heroCurrentStack != null) {
            builder.stacks(heroCurrentStack.doubleValue());
        }
        
        if (bigBlind != null) {
            builder.bigBlind(bigBlind.doubleValue());
        }

        // 转换为BB单位（兼容字段）
        if (hand.getCurrentPot() != null && bigBlind != null && bigBlind.compareTo(BigDecimal.ZERO) > 0) {
            potBbValue = hand.getCurrentPot().divide(bigBlind, 2, RoundingMode.HALF_UP).doubleValue();
            builder.potBb(potBbValue);
        }
        if (heroCurrentStack != null && bigBlind != null && bigBlind.compareTo(BigDecimal.ZERO) > 0) {
            stackBbValue = heroCurrentStack.divide(bigBlind, 2, RoundingMode.HALF_UP).doubleValue();
            builder.stacksBb(stackBbValue);
        }

        // 计算并添加SPR（筹码底池比）
        if (stackBbValue != null && potBbValue != null && potBbValue > 0) {
            builder.spr(Math.round(stackBbValue / potBbValue * 10.0) / 10.0);
        }

        // 玩家底牌
        if (holeCards != null && !holeCards.isEmpty()) {
            builder.holeCards(holeCards); // Use new field
            // Backward compatible: also set deprecated field
            builder.heroHole(String.join(" ", holeCards));
        }

        // 公共牌
        List<String> boardCards = parseBoardCards(hand);
        if (boardCards != null && !boardCards.isEmpty()) {
            builder.board(String.join(" ", boardCards));
        }

        // 活跃玩家数
        if (hand.getActivePlayers() != null) {
            builder.playersAlive(hand.getActivePlayers());
        }

        // 数学分析结果（完整版）
        if (mathResult != null && mathResult.hasValidEquity) {
            // 玩家权益（胜率+平局率/2）
            builder.playerEquity(mathResult.equity); // Use new field
            builder.heroEquity(mathResult.equity); // Backward compatible

            // 计算决策场景（V4.10: 优先使用客户端提供的值）
            BigDecimal toCall;
            if (clientRequest != null && clientRequest.getToCall() != null) {
                // 使用客户端提供的实时 to_call 值
                toCall = BigDecimal.valueOf(clientRequest.getToCall());
            } else {
                // 从数据库计算（向后兼容）
                toCall = calculateToCall(hand, actions);
            }
            
            boolean facingBet = (toCall != null && toCall.compareTo(BigDecimal.ZERO) > 0);
            boolean canCheck = !facingBet;

            builder.facingBet(facingBet);
            builder.canCheck(canCheck);

            if (facingBet) {
                // Hero面临下注，需要跟注
                builder.amountToCall(toCall.doubleValue());

                // 底池赔率详细说明（包含数学建议）
                if (hand.getCurrentPot() != null) {
                    double potOdds =
                        toCall.divide(hand.getCurrentPot().add(toCall), 4, RoundingMode.HALF_UP).doubleValue();
                    String mathAdvice = mathResult.equity > potOdds ? "应跟注/加注" : "应弃牌";
                    builder.potOddsNote(
                        String.format("需跟注$%.0f，底池$%.0f（需%.1f%%胜率 vs 实际%.1f%%）→ %s", toCall.doubleValue(),
                            hand.getCurrentPot().doubleValue(), potOdds * 100, mathResult.equity * 100, mathAdvice));
                }
            } else {
                // Hero可以免费过牌
                builder.amountToCall(0.0);
                builder.potOddsNote("无需跟注（可过牌或下注）");
            }
        }

        // 行动历史（带位置信息的行动序列）
        List<PokerAIRequest.ActionHistory> actionHistory = buildActionHistory(hand, actions);
        if (!actionHistory.isEmpty()) {
            builder.actionsHistory(actionHistory);
        }

        // 构建玩家摘要信息（用于Prompt显示）
        String playersSummary = buildPlayersSummary(hand, actions, heroPlayerId);
        if (playersSummary != null) {
            builder.actionsBrief(playersSummary);
        }
        
        // V4.35: 设置返回语言（从客户端请求获取，默认中文）
        if (clientRequest != null && clientRequest.getLanguage() != null) {
            builder.language(clientRequest.getLanguage());
        } else {
            builder.language("zh"); // 默认中文
        }

        return builder.build();
    }

    /**
     * 构建玩家摘要信息（包括座位、位置、筹码、状态）
     * 
     * @param hand 手牌记录
     * @param actions 行动记录列表
     * @param heroPlayerId Hero的player_id（用于正确标记isHero）
     */
    private String buildPlayersSummary(PokerHand hand, List<PokerHandAction> actions, String heroPlayerId) {
        try {
            String playersInfoJson = hand.getPlayersInfo();
            if (playersInfoJson == null || playersInfoJson.isEmpty()) {
                return null;
            }

            List<Map<String, Object>> playersList = JsonUtil.fromJson(playersInfoJson, List.class);
            if (playersList == null || playersList.isEmpty()) {
                return null;
            }

            // 构建玩家状态信息和当前筹码
            Set<Integer> foldedSeats = new HashSet<>();
            Set<Integer> activeSeats = new HashSet<>();
            Map<Integer, BigDecimal> currentStacks = new HashMap<>();  // 座位号 -> 当前筹码

            // 遍历所有action，获取每个玩家的最新筹码（从stack_after字段）
            for (PokerHandAction action : actions) {
                Integer seat = action.getSeatNumber();
                if (seat != null) {
                    if ("fold".equalsIgnoreCase(action.getActionType())) {
                        foldedSeats.add(seat);
                    } else {
                        activeSeats.add(seat);
                    }
                    
                    // 更新当前筹码（使用最新的stack_after）
                    if (action.getStackAfter() != null) {
                        currentStacks.put(seat, action.getStackAfter());
                    }
                }
            }

            // 构建玩家摘要（JSON格式，方便解析）
            StringBuilder summary = new StringBuilder("[");
            boolean first = true;

            for (Map<String, Object> player : playersList) {
                if (!first)
                    summary.append(",");
                first = false;

                Object seatObj = player.get("seat_number");
                Object posObj = player.get("position");
                Object stackObj = player.get("stack");
                if (stackObj == null) {
                    stackObj = player.get("stack_start");
                }
                Object playerIdObj = player.get("player_id");

                int seat = seatObj instanceof Integer ? (Integer)seatObj : Integer.parseInt(seatObj.toString());
                String position = posObj != null ? posObj.toString() : "P" + seat;
                String playerIdInList = playerIdObj != null ? playerIdObj.toString() : "";

                // 优先使用action记录中的当前筹码，否则使用初始筹码
                double stack = currentStacks.containsKey(seat) 
                    ? currentStacks.get(seat).doubleValue()
                    : (stackObj instanceof Number ? ((Number)stackObj).doubleValue() : 0.0);

                // 判断状态
                String status = foldedSeats.contains(seat) ? "已弃牌" : activeSeats.contains(seat) ? "在局" : "未行动";

                // 是否是Hero（使用 playerId 判断，而不是 heroSeatNumber）
                boolean isHero = (heroPlayerId != null && heroPlayerId.equals(playerIdInList));

                summary.append("{");
                summary.append("\"seat\":").append(seat).append(",");
                summary.append("\"pos\":\"").append(position).append("\",");
                summary.append("\"player_id\":\"").append(playerIdInList).append("\",");
                summary.append("\"stack\":").append(stack).append(",");
                summary.append("\"status\":\"").append(status).append("\",");
                summary.append("\"isHero\":").append(isHero);
                summary.append("}");
            }

            summary.append("]");
            return summary.toString();

        } catch (Exception e) {
            logger.warn("无法构建玩家摘要信息", e);
            return null;
        }
    }

    /**
     * 构建行动历史（包含位置映射）
     */
    private List<PokerAIRequest.ActionHistory> buildActionHistory(PokerHand hand, List<PokerHandAction> actions) {
        List<PokerAIRequest.ActionHistory> history = new ArrayList<>();

        // 解析玩家位置信息
        Map<Integer, String> seatToPosition = extractSeatPositions(hand);

        for (PokerHandAction action : actions) {
            if (action.getActionType() == null)
                continue;

            PokerAIRequest.ActionHistory ah = new PokerAIRequest.ActionHistory();

            // 使用实际位置名称（SB, BB, UTG等）
            String position = seatToPosition.getOrDefault(action.getSeatNumber(), "P" + action.getSeatNumber());
            ah.setPosition(position);
            ah.setAction(action.getActionType());
            ah.setStreet(action.getStreet());

            if (action.getAmount() != null && action.getAmount().compareTo(BigDecimal.ZERO) > 0) {
                ah.setSize("$" + action.getAmount().intValue());
            }

            history.add(ah);
        }

        return history;
    }

    /**
     * 从hand的players_info中提取座位号→位置的映射
     */
    private Map<Integer, String> extractSeatPositions(PokerHand hand) {
        Map<Integer, String> seatToPosition = new HashMap<>();

        try {
            String playersInfoJson = hand.getPlayersInfo();
            if (playersInfoJson != null && !playersInfoJson.isEmpty()) {
                List<Map<String, Object>> playersList = JsonUtil.fromJson(playersInfoJson, List.class);
                if (playersList != null) {
                    for (Map<String, Object> player : playersList) {
                        Object seatObj = player.get("seat_number");
                        Object posObj = player.get("position");
                        if (seatObj != null && posObj != null) {
                            int seat =
                                seatObj instanceof Integer ? (Integer)seatObj : Integer.parseInt(seatObj.toString());
                            String position = posObj.toString();
                            seatToPosition.put(seat, position);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.warn("无法解析players_info，使用座位号作为位置标识", e);
        }

        return seatToPosition;
    }

    /**
     * 🎲 创建模拟专业扑克AI结果（用于测试）
     */
    private PokerAIResponse createMockPokerAIResult(GameDecisionContext context) {
        PokerAIResponse result = new PokerAIResponse();
        // 不再设置providerId和model，已删除这些字段

        // 基于胜率创建简单的模拟决策
        String action;
        String size;
        String reasons; // V3.4: 改为单条字符串

        if (context.getEquity() > 0.7) {
            action = "raise";
            size = "0.66p";
            reasons = "胜率" + String.format("%.0f%%", context.getEquity() * 100) + "，强牌价值下注";
        } else if (context.getEquity() > 0.5) {
            action = "call";
            size = "0";
            reasons = "胜率" + String.format("%.0f%%", context.getEquity() * 100) + "，中等牌力跟注";
        } else if (context.getEquity() > 0.3) {
            action = "call";
            size = "0";
            reasons = "底池赔率有利，边际牌力控池";
        } else {
            action = "fold";
            size = "0";
            reasons = "胜率过低，避免亏损弃牌";
        }

        result.setAction(action);
        result.setSize(size);
        result.setReasons(reasons);

        return result;
    }

    /**
     * 🛡️ 创建fallback专业扑克决策 (V3.4)
     */
    private PokerAIResponse createFallbackPokerDecision(GameDecisionContext context) {
        PokerAIResponse result = new PokerAIResponse();
        // 不再设置providerId和model，已删除这些字段

        // 保守的fold决策
        result.setAction("fold");
        result.setSize("0");
        result.setReasons("AI模型错误，保守弃牌策略"); // V3.4: 单条字符串

        return result;
    }

    /**
     * 🔄 转换专业扑克AI结果为标准分析响应
     */
    /**
     * @deprecated V4.9: 已废弃，使用 convertToAIResult
     */
    @Deprecated
    private void convertPokerAIToAnalysisResponse(PokerAIResponse pokerAI, AIAnalysisResponse response,
        MathAnalysisResult mathResult) {
        // V4.9: 该方法已废弃
        logger.debug("convertPokerAIToAnalysisResponse is deprecated in V4.9");
    }

    /**
     * 🔧 转换尺寸描述为具体金额
     * @deprecated V4.9: 已废弃
     */
    @Deprecated
    private BigDecimal convertSizeToAmount(String size, Object gameSnapshot) {
        // 处理 null 和 "none"
        if (size == null || "none".equals(size)) {
            return BigDecimal.ZERO;
        }

        // 使用默认底池值，因为GameSnapshot可能还未创建
        BigDecimal currentPot = BigDecimal.valueOf(300); // 默认底池大小

        switch (size) {
            case "0.25p":
                return currentPot.multiply(BigDecimal.valueOf(0.25));
            case "0.33p":
                return currentPot.multiply(BigDecimal.valueOf(0.33));
            case "0.5p":
                return currentPot.multiply(BigDecimal.valueOf(0.5));
            case "0.66p":
                return currentPot.multiply(BigDecimal.valueOf(0.66));
            case "0.75p":
                return currentPot.multiply(BigDecimal.valueOf(0.75));
            case "1.0p":
                return currentPot;
            case "1.25p":
                return currentPot.multiply(BigDecimal.valueOf(1.25));
            default:
                // 处理 "Xbb" 格式
                if (size.endsWith("bb")) {
                    String bbValue = size.substring(0, size.length() - 2);
                    try {
                        return new BigDecimal(bbValue).multiply(BigDecimal.valueOf(200)); // 假设大盲注为200
                    } catch (NumberFormatException e) {
                        return currentPot.multiply(BigDecimal.valueOf(0.5)); // 默认0.5倍底池
                    }
                }
                return currentPot.multiply(BigDecimal.valueOf(0.5));
        }
    }

    private String generateHandStrengthReasoning(GameContext context) {
        double strength = context.getHandStrength();
        if (strength >= 0.8) {
            return "Strong hand with high equity, excellent for value betting";
        } else if (strength >= 0.6) {
            return "Good hand with decent equity, suitable for aggressive play";
        } else if (strength >= 0.4) {
            return "Marginal hand, decision depends on pot odds and position";
        } else {
            return "Weak hand with low equity, consider folding or bluffing";
        }
    }

    private String generatePositionReasoning(GameContext context) {
        String position = context.getHeroPosition();
        switch (position) {
            case "BTN":
                return "Button position provides maximum information and betting control";
            case "CO":
                return "Cutoff position offers good stealing opportunities";
            case "SB":
                return "Small blind position is challenging due to poor post-flop position";
            case "BB":
                return "Big blind gets favorable pot odds but poor position post-flop";
            default:
                return "Early position requires stronger hands for aggressive play";
        }
    }

    private String generateOpponentReasoning(GameContext context) {
        return "Opponents showing typical tight-aggressive tendencies";
    }

    private String generatePotOddsReasoning(GameContext context) {
        BigDecimal potOdds = context.getPotOdds();
        return String.format("Current pot odds %.1f:1 %s calling requirements", potOdds.doubleValue(),
            potOdds.compareTo(BigDecimal.valueOf(3.0)) >= 0 ? "meet" : "don't meet");
    }

    private String generateBoardReasoning(GameContext context) {
        // 简化的牌面分析
        return "Board texture analysis shows moderate connectivity with few draws";
    }

    /**
     * @deprecated V4.9: 已废弃
     */
    @Deprecated
    private List<String> generateKeyFactors(GameContext context, Object recommendation) {
        // V4.9: 该方法已废弃
        logger.debug("generateKeyFactors is deprecated in V4.9");
        return List.of();
    }

    // 辅助方法

    /**
     * AI分析请求必要校验
     *
     * 校验策略： 1. 基础参数校验 - 手牌ID和Hero玩家ID不能为空 2. 手牌存在性校验 - 确保手牌存在 3. 手牌状态校验 - 确保手牌还在进行中 4. Hero权限校验 - 确保这个手牌有Hero信息 5.
     * Hero身份验证 - 确保请求者就是Hero本人 6. Hero行动权校验 - 确保轮到Hero行动
     */
    private void validateAnalysisRequest(AIAnalysisRequest request) {
        // 1. 基础参数校验
        if (request == null) {
            throw new IllegalArgumentException("Request cannot be null");
        }
        if (request.getHandId() == null) {
            throw new IllegalArgumentException("Hand ID is required");
        }
        // 支持新旧字段
        String playerId = request.getPlayerId();
        if (playerId == null || playerId.trim().isEmpty()) {
            throw new IllegalArgumentException("Player ID is required");
        }
        
        // V4.7: non-hero perspective架构，hole_cards为可选字段
        // 如果提供了hole_cards，则进行精确分析；如果没有，则基于公共信息分析

        // 2. 手牌存在性校验
        PokerHand hand = findHandByHandId(request.getHandId());
        if (hand == null) {
            throw new IllegalArgumentException("Hand not found: " + request.getHandId());
        }

        // 3. 手牌状态校验 - 确保手牌还在进行中
        if (!hand.isActive()) {
            throw new IllegalArgumentException("Hand is not active for AI analysis: " + request.getHandId());
        }

        // 4. 玩家权限校验 - 简化处理
        // 注：不再强制要求Hero字段，支持多人视角

        // 5. 玩家身份验证 - 简化处理，仅记录日志
        // TODO: 可选择性地验证playerId是否在hand的玩家列表中
        logger.debug("AI analysis request validated for hand: {}, player: {}", 
            request.getHandId(), playerId);
    }

    private PokerHand findHandById(Long handRecordId) {
        return pokerHandDao.findById(handRecordId).orElse(null);
    }
    
    private PokerHand findHandByHandId(String handId) {
        return pokerHandDao.findByHandId(handId).orElse(null);
    }

    private List<PokerHandAction> getHandActions(Long handRecordId) {
        return pokerHandActionDao.findByHandId(handRecordId);
    }

    /**
     * 从手牌记录中提取Hero玩家ID
     *
     * @param hand 手牌记录
     * @return Hero的playerId，如果不存在则返回null
     */
    private String extractHeroPlayerIdFromHand(PokerHand hand) {
        try {
            String playersInfo = hand.getPlayersInfo();
            if (playersInfo == null || playersInfo.trim().isEmpty()) {
                logger.warn("Players info is empty for hand: {}", hand.getId());
                return null;
            }

            // 解析players_info JSON，查找Hero玩家
            Object playersObj = objectMapper.readValue(playersInfo, Object.class);
            if (playersObj instanceof java.util.List) {
                @SuppressWarnings("unchecked") java.util.List<java.util.Map<String, Object>> playersList =
                    (java.util.List<java.util.Map<String, Object>>)playersObj;

                for (java.util.Map<String, Object> player : playersList) {
                    Boolean isHero = (Boolean)player.get("is_hero"); // 修复：使用正确的JSON字段名
                    if (Boolean.TRUE.equals(isHero)) {
                        return (String)player.get("player_id"); // 修复：使用正确的JSON字段名
                    }
                }
            }

            logger.warn("No Hero player found in players_info for hand: {}", hand.getId());
            return null;

        } catch (Exception e) {
            logger.error("Failed to extract Hero player ID from hand: {}", hand.getId(), e);
            return null;
        }
    }

    // === 新增的数学分析辅助方法 ===

    /**
     * 解析Hero底牌
     */
    @Deprecated
    private List<String> parseHeroCards(PokerHand hand) {
        // Deprecated: Use parseHeroCardsFromString with explicit hole cards instead
        return null;
    }
    
    /**
     * 解析底牌字符串（支持JSON数组格式）
     */
    private List<String> parseHeroCardsFromString(String heroHole) {
        try {
            if (heroHole != null && !heroHole.trim().isEmpty()) {
                // JSON格式：["As", "Ks"]
                return objectMapper.readValue(heroHole,
                    objectMapper.getTypeFactory().constructCollectionType(List.class, String.class));
            }
        } catch (Exception e) {
            logger.debug("Failed to parse hole cards: {}", e.getMessage());
        }
        return null;
    }
    
    /**
     * 从playersInfo JSON中提取玩家位置
     */
    private String extractPlayerPosition(PokerHand hand, String playerId) {
        try {
            if (hand.getPlayersInfo() != null && !hand.getPlayersInfo().trim().isEmpty() && playerId != null) {
                // playersInfo格式: [{"player_id":"p1","position":"BTN",...}, ...]
                com.fasterxml.jackson.databind.JsonNode playersArray = objectMapper.readTree(hand.getPlayersInfo());
                if (playersArray.isArray()) {
                    for (com.fasterxml.jackson.databind.JsonNode playerNode : playersArray) {
                        if (playerId.equals(playerNode.path("player_id").asText())) {
                            return playerNode.path("position").asText("Unknown");
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.debug("Failed to extract player position for playerId {}: {}", playerId, e.getMessage());
        }
        return "Unknown";
    }

    /**
     * 解析公共牌
     */
    private List<String> parseBoardCards(PokerHand hand) {
        try {
            String boardCards = hand.getBoardCards();
            if (boardCards != null && !boardCards.trim().isEmpty()) {
                // 格式可能是 "Ah 7s 2c" 或 ["Ah", "7s", "2c"]
                if (boardCards.startsWith("[")) {
                    // JSON数组格式
                    return objectMapper.readValue(boardCards,
                        objectMapper.getTypeFactory().constructCollectionType(List.class, String.class));
                } else {
                    // 空格分隔格式
                    return Arrays.asList(boardCards.trim().split("\\s+"));
                }
            }
        } catch (Exception e) {
            logger.debug("Failed to parse board cards: {}", e.getMessage());
        }
        return new ArrayList<>(); // 翻牌前返回空列表
    }

    /**
     * 计算活跃对手数量
     */
    private int calculateActiveOpponents(PokerHand hand, List<PokerHandAction> actions) {
        try {
            // 从players_info解析总玩家数
            String playersInfo = hand.getPlayersInfo();
            if (playersInfo != null && !playersInfo.trim().isEmpty()) {
                Object playersObj = objectMapper.readValue(playersInfo, Object.class);
                if (playersObj instanceof List) {
                    @SuppressWarnings("unchecked") List<Map<String, Object>> playersList =
                        (List<Map<String, Object>>)playersObj;

                    // 计算未弃牌的玩家数（不包括Hero）
                    Set<String> foldedPlayers = new HashSet<>();
                    for (PokerHandAction action : actions) {
                        if ("fold".equals(action.getActionType())) {
                            foldedPlayers.add(action.getPlayerId());
                        }
                    }

                    int activeOpponents = 0;
                    for (Map<String, Object> player : playersList) {
                        String playerId = (String)player.get("player_id");
                        Boolean isHero = (Boolean)player.get("is_hero");

                        if (!Boolean.TRUE.equals(isHero) && !foldedPlayers.contains(playerId)) {
                            activeOpponents++;
                        }
                    }

                    return Math.max(1, activeOpponents); // 至少1个对手
                }
            }
        } catch (Exception e) {
            logger.debug("Failed to calculate active opponents: {}", e.getMessage());
        }

        // 默认值
        return Math.max(1, hand.getPlayersDealt() - 1);
    }

    /**
     * 计算Hero需要跟注的金额（基于当前街道）
     */
    private BigDecimal calculateToCall(PokerHand hand, List<PokerHandAction> actions) {
        try {
            String currentStreet = hand.getCurrentStreet();
            String heroPlayerId = findHeroPlayerIdFromActions(hand, actions);

            if (heroPlayerId == null || currentStreet == null) {
                return BigDecimal.ZERO;
            }

            // 计算当前街道每个玩家的总投入
            Map<String, BigDecimal> playerInvested = new HashMap<>();
            for (PokerHandAction action : actions) {
                String actionStreet = action.getStreet();
                BigDecimal actionAmount = action.getAmount();
                String actionPlayerId = action.getPlayerId();

                // 匹配当前街道的action（包括盲注）
                if (currentStreet.equals(actionStreet) && actionAmount != null && actionPlayerId != null) {
                    playerInvested.put(actionPlayerId,
                        playerInvested.getOrDefault(actionPlayerId, BigDecimal.ZERO).add(actionAmount));
                }
            }

            // 找到最大下注金额
            BigDecimal maxBet = playerInvested.values().stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO);

            // Hero当前投入
            BigDecimal heroInvested = playerInvested.getOrDefault(heroPlayerId, BigDecimal.ZERO);

            // 需要跟注的金额 = 最大下注 - Hero已投入
            return maxBet.subtract(heroInvested).max(BigDecimal.ZERO);
        } catch (Exception e) {
            logger.error("Failed to calculate to call: {}", e.getMessage(), e);
            return BigDecimal.ZERO;
        }
    }

    /**
     * 从players_info中获取Hero的当前筹码
     */
    private BigDecimal getHeroCurrentStack(PokerHand hand) {
        try {
            String playersInfoJson = hand.getPlayersInfo();
            
            if (playersInfoJson != null && !playersInfoJson.isEmpty()) {
                List<Map<String, Object>> players = JsonUtil.fromJson(playersInfoJson, List.class);
                
                if (players != null) {
                    for (Map<String, Object> player : players) {
                        Boolean isHero = (Boolean)player.get("is_hero");
                        if (isHero != null && isHero) {
                            Object stackObj = player.get("stack");
                            if (stackObj != null) {
                                if (stackObj instanceof Number) {
                                    return BigDecimal.valueOf(((Number)stackObj).doubleValue());
                                } else if (stackObj instanceof String) {
                                    return new BigDecimal((String)stackObj);
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.warn("Failed to extract Hero's current stack from players_info", e);
        }
        
        // 如果无法从players_info获取，回退到初始筹码（虽然不准确，但至少有值）
        return hand.getHeroStackStart();
    }

    /**
     * 从players_info中找到Hero的playerId
     */
    private String findHeroPlayerIdFromActions(PokerHand hand, List<PokerHandAction> actions) {
        try {
            // 从players_info JSON中找到isHero=true的玩家
            String playersInfoJson = hand.getPlayersInfo();
            
            if (playersInfoJson != null && !playersInfoJson.isEmpty()) {
                List<Map<String, Object>> players = JsonUtil.fromJson(playersInfoJson, List.class);
                
                if (players != null) {
                    for (Map<String, Object> player : players) {
                        // 使用 snake_case 字段名（数据库中的实际格式）
                        Boolean isHero = (Boolean)player.get("is_hero");
                        if (isHero != null && isHero) {
                            return (String)player.get("player_id");
                        }
                    }
                }
            }

            // 如果没找到，尝试通过heroSeatNumber匹配
            Integer heroSeat = hand.getHeroSeatNumber();
            if (heroSeat != null && playersInfoJson != null) {
                List<Map<String, Object>> players = JsonUtil.fromJson(playersInfoJson, List.class);
                if (players != null) {
                    for (Map<String, Object> player : players) {
                        Object seatObj = player.get("seat");
                        if (seatObj != null && heroSeat.equals(((Number)seatObj).intValue())) {
                            return (String)player.get("playerId");
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.debug("Failed to find hero player id: {}", e.getMessage());
        }
        return null;
    }

    private GameContext buildGameContext(PokerHand hand, List<PokerHandAction> actions) {
        GameContext context = new GameContext();
        context.setHandId(hand.getId());
        context.setCurrentStreet(hand.getCurrentStreet());
        context.setCurrentPot(hand.getCurrentPot() != null ? hand.getCurrentPot() : BigDecimal.ZERO);
        context.setHeroPosition(hand.getHeroPosition());
        context.setHeroStack(hand.getHeroStackStart() != null ? hand.getHeroStackStart() : BigDecimal.valueOf(20000));

        // 计算需要跟注的数量
        BigDecimal toCallAmount = calculateToCall(hand, actions);

        // 使用生产版本的手牌强度计算（基于蒙特卡洛模拟）
        double handStrength = calculateProductionHandStrength(hand, actions);
        context.setHandStrength(handStrength);

        // 底池赔率计算 (Pot Odds)
        // 公式：底池赔率 = 跟注成本 / (当前底池 + 跟注成本)
        // 例如：底池$100，需跟注$25，底池赔率 = 25/(100+25) = 0.2 = 20%
        // 决策规则：如果胜率 > 底池赔率，从数学上应该跟注
        if (toCallAmount.compareTo(BigDecimal.ZERO) > 0) {
            BigDecimal totalPot = context.getCurrentPot().add(toCallAmount);
            BigDecimal potOdds = toCallAmount.divide(totalPot, 4, RoundingMode.HALF_UP);
            context.setPotOdds(potOdds);
        } else {
            context.setPotOdds(BigDecimal.ZERO);
        }
        return context;
    }

    /**
     * 计算生产版本的手牌强度（基于蒙特卡洛模拟） 使用PokerMathCalculator进行精确的胜率计算
     * @deprecated Use performMathematicalAnalysis instead
     */
    @Deprecated
    private double calculateProductionHandStrength(PokerHand hand, List<PokerHandAction> actions) {
        try {
            // 解析Hero底牌
            List<String> heroCards = parseHeroCards(hand);
            if (heroCards == null || heroCards.size() != 2) {
                return 0.5; // 无法解析底牌时返回中等牌力
            }

            // 解析公共牌
            List<String> boardCards = parseBoardCards(hand);

            // 计算活跃对手数量
            int opponentCount = calculateActiveOpponents(hand, actions);

            // 使用蒙特卡洛模拟计算胜率（作为手牌强度指标）
            // 使用较少的模拟次数以提高性能（1000次）
            PokerMathCalculator.EquityResult equityResult =
                mathCalculator.calculateEquity(heroCards, boardCards, opponentCount, 1000);

            // 返回权益作为手牌强度
            return equityResult.getEquity();

        } catch (Exception e) {
            logger.debug("Failed to calculate production hand strength: {}", e.getMessage());
            return 0.5; // 计算失败时返回中等牌力
        }
    }

    /**
     * @deprecated V4.9: 已废弃
     */
    @Deprecated
    private Object buildGameSnapshot(PokerHand hand, GameContext context) {
        logger.debug("buildGameSnapshot is deprecated in V4.9");
        return null;
    }

    /**
     * @deprecated V4.9: 已废弃
     */
    @Deprecated
    private Object calculateStatistics(GameContext context) {
        logger.debug("calculateStatistics is deprecated in V4.9");
        return null;
    }

    private void saveAnalysisSnapshot(PokerHand hand, AIAnalysisRequest request, AIAnalysisResponse response) {
        // TODO: 实现分析结果保存到数据库
        // 创建PokerDecisionSnapshot记录
    }

    private Long generateAnalysisId() {
        return System.currentTimeMillis(); // 简化的ID生成
    }

    private BigDecimal calculateCpuUsage() {
        return BigDecimal.valueOf(0.45); // 模拟CPU使用率
    }

    /**
     * 根据街道获取最优模拟次数
     *
     * 策略：早期街道（信息少）用更少模拟，后期街道（信息多）用更多模拟
     *
     * @param street 当前街道
     * @return 模拟次数
     */
    private int getOptimalSimulationCount(String street) {
        if (street == null) {
            return 5000;
        }

        switch (street.toLowerCase()) {
            case "preflop":
                return 5000;  // 快速决策（节省60ms）
            case "flop":
                return 7000;  // 中等精度（节省30ms）
            case "turn":
            case "river":
                return 10000; // 高精度（关键决策点）
            default:
                return 5000;  // 默认快速模式
        }
    }

    /**
     * 转换复杂AI响应为极简决策（V4.1增强版）
     * @deprecated V4.9: 已废弃，V4.9 使用新的响应结构
     */
    @Deprecated
    public SimpleAIDecision convertToSimpleDecision(AIAnalysisResponse fullResponse, PokerHand hand, 
                                                      String playerId, String playerPos) {
        // V4.9: 该方法已废弃，但保留兼容性
        logger.debug("convertToSimpleDecision is deprecated in V4.9");
        
        SimpleAIDecision.SimpleAIDecisionBuilder builder = SimpleAIDecision.builder();
        
        // 从新结构中提取数据（V4.9 极简版）
        if (fullResponse.getRecommendedSource() != null) {
            if ("ai".equals(fullResponse.getRecommendedSource()) && fullResponse.getAi() != null) {
                builder.action(fullResponse.getAi().getAction());
                builder.confidence(fullResponse.getAi().getConfidence());
                builder.size(fullResponse.getAi().getAmount() != null ? 
                    fullResponse.getAi().getAmount().toString() : "0");
            } else if (fullResponse.getGto() != null) {
                // GTO 现在是概率分布，取频率最高的行动
                String gtoAction = fullResponse.getGto().entrySet().stream()
                    .max(Map.Entry.comparingByValue())
                    .map(Map.Entry::getKey)
                    .orElse("fold");
                builder.action(gtoAction);
                builder.size("0");  // GTO 不返回具体金额
            }
        }
        
        return builder.build();
    }
    
    /**
     * 从playersInfo JSON中提取玩家座位号
     */
    private Integer extractPlayerSeatNumber(PokerHand hand, String playerId) {
        try {
            String playersInfoJson = hand.getPlayersInfo();
            if (playersInfoJson == null || playersInfoJson.isEmpty()) {
                return null;
            }
            
            com.fasterxml.jackson.databind.JsonNode playersArray = objectMapper.readTree(playersInfoJson);
            if (!playersArray.isArray()) {
                return null;
            }
            
            for (com.fasterxml.jackson.databind.JsonNode playerNode : playersArray) {
                String nodePlayerId = playerNode.path("player_id").asText();
                if (playerId.equals(nodePlayerId)) {
                    return playerNode.path("seat_number").asInt();
                }
            }
        } catch (Exception e) {
            logger.warn("Failed to extract seat number for player: {}", playerId, e);
        }
        return null;
    }

    /**
     * 游戏上下文内部类
     */
    private static class GameContext {
        private Long handId;
        private String currentStreet;
        private BigDecimal currentPot;
        private String heroPosition;
        private BigDecimal heroStack;
        private double handStrength;
        private BigDecimal potOdds;

        // Getters and Setters
        public Long getHandId() {
            return handId;
        }

        public void setHandId(Long handId) {
            this.handId = handId;
        }

        public String getCurrentStreet() {
            return currentStreet;
        }

        public void setCurrentStreet(String currentStreet) {
            this.currentStreet = currentStreet;
        }

        public BigDecimal getCurrentPot() {
            return currentPot;
        }

        public void setCurrentPot(BigDecimal currentPot) {
            this.currentPot = currentPot;
        }

        public String getHeroPosition() {
            return heroPosition;
        }

        public void setHeroPosition(String heroPosition) {
            this.heroPosition = heroPosition;
        }

        public BigDecimal getHeroStack() {
            return heroStack;
        }

        public void setHeroStack(BigDecimal heroStack) {
            this.heroStack = heroStack;
        }

        public double getHandStrength() {
            return handStrength;
        }

        public void setHandStrength(double handStrength) {
            this.handStrength = handStrength;
        }

        public BigDecimal getPotOdds() {
            return potOdds;
        }

        public void setPotOdds(BigDecimal potOdds) {
            this.potOdds = potOdds;
        }
    }

    /**
     * 使用 SiliconFlow HTTP 客户端进行 AI 决策
     * 
     * @param request AI请求对象
     * @param gtoFrequencies GTO频率分布（V4.10新增）
     */
    private PokerAIResponse callSiliconFlowAI(PokerAIRequest request, Map<String, Double> gtoFrequencies) {
        try {
            // V4.10: 使用带GTO频率的 Prompt 构建逻辑
            String prompt = pokerAIService.buildPokerPrompt(request, gtoFrequencies);
            
            // 打印完整 Prompt（用于调试和分析）
            logger.info("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
            logger.info("【AI Prompt】");
            logger.info("  Street: {}", request.getStreet());
            logger.info("  Hole Cards: {}", request.getHoleCards());
            logger.info("  Board: {}", request.getBoard());
            logger.info("  Pot: ${}", request.getPot());
            logger.info("Prompt内容:\n{}", prompt);
            logger.info("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
            
            // 调用 SiliconFlow API（不需要 System Prompt，因为 prompt 已经包含角色定位）
            String aiResponse = siliconFlowClient.chatCompletion("", prompt);
            
            // 清理 Markdown 代码块标记（某些模型会返回 ```json ... ``` 格式）
            String cleanedResponse = aiResponse;
            if (aiResponse.trim().startsWith("```")) {
                // 移除开头的 ```json 或 ```
                cleanedResponse = aiResponse.replaceFirst("^\\s*```(?:json)?\\s*", "");
                // 移除结尾的 ```
                cleanedResponse = cleanedResponse.replaceFirst("```\\s*$", "");
                cleanedResponse = cleanedResponse.trim();
            }
            
            // 清理字段值中的换行符（避免JSON解析错误）
            // 注意：只清理字段值中的换行符，不影响JSON结构
            cleanedResponse = cleanedResponse.replaceAll("(?<=: \")([^\"]*?)\\n([^\"]*?)(?=\")", "$1 $2");
            
            // 解析响应为 PokerAIResponse
            PokerAIResponse result = JsonUtil.fromJson(cleanedResponse, PokerAIResponse.class);
            
            if (result == null || result.getAction() == null) {
                logger.warn("SiliconFlow returned invalid response, using fallback");
                throw new RuntimeException("Invalid SiliconFlow response");
            }
            
            return result;
        } catch (Exception e) {
            logger.error("SiliconFlow AI call failed: {}", e.getMessage(), e);
            throw new RuntimeException("SiliconFlow AI call failed", e);
        }
    }

    /**
     * 创建极端保守的响应（最后兜底）V3.4
     */
    private PokerAIResponse createUltraConservativeResponse() {
        PokerAIResponse response = new PokerAIResponse();
        response.setAction("fold");
        response.setSize(null);
        // V3.4: 合并为单条理由
        response.setReasons("系统错误，保守弃牌避免损失");
        return response;
    }
    
    // ========== V4.9 终极简化方法 ==========
    
    /**
     * V4.9: 计算 GTO 频率分布（极简版）
     * 只返回概率分布，不返回具体决策
     */
    private Map<String, Double> calculateGTOFrequencies(double equity, String position) {
        Map<String, Double> frequencies = new HashMap<>();
        
        // 基于胜率的基础频率
        if (equity > 0.65) {
            // 强牌：主要加注
            frequencies.put("raise", 0.80);
            frequencies.put("call", 0.15);
            frequencies.put("fold", 0.05);
        } else if (equity > 0.45) {
            // 中等牌：混合策略
            frequencies.put("raise", 0.40);
            frequencies.put("call", 0.50);
            frequencies.put("fold", 0.10);
        } else if (equity > 0.30) {
            // 弱牌：主要跟注或弃牌
            frequencies.put("raise", 0.10);
            frequencies.put("call", 0.30);
            frequencies.put("fold", 0.60);
        } else {
            // 极弱牌：主要弃牌
            frequencies.put("raise", 0.05);
            frequencies.put("call", 0.10);
            frequencies.put("fold", 0.85);
        }
        
        // 位置调整
        if ("BTN".equals(position) || "CO".equals(position)) {
            // 位置优势：增加激进频率
            double raiseFreq = frequencies.get("raise");
            double foldFreq = frequencies.get("fold");
            frequencies.put("raise", Math.min(0.95, raiseFreq + 0.10));
            frequencies.put("fold", Math.max(0.0, foldFreq - 0.10));
        }
        
        return frequencies;
    }
    
    /**
     * V4.9: 从频率分布中获取最优行动（频率最高的）
     */
    private String getOptimalAction(Map<String, Double> frequencies) {
        return frequencies.entrySet().stream()
            .max(Map.Entry.comparingByValue())
            .map(Map.Entry::getKey)
            .orElse("fold");
    }
    
    /**
     * V4.9: 转换 AI 响应为 AIDecision（极简版）
     * V4.30: 修复Confidence值 - 使用GTO概率而非Equity
     */
    private AIAnalysisResponse.AIDecision convertToAIDecision(PokerAIResponse aiResponse, 
            MathAnalysisResult mathResult, Map<String, Double> gtoFrequencies) {
        
        AIAnalysisResponse.AIDecision decision = new AIAnalysisResponse.AIDecision();
        
        // 行动和金额
        decision.setAction(aiResponse.getAction());
        BigDecimal amount = convertSizeToAmount(aiResponse.getSize(), mathResult);
        decision.setAmount(amount != null ? amount.intValue() : 0);
        
        // V4.30修复: 信心度应该使用GTO概率，而不是Equity
        // Confidence表示"对这个决策的确定度"，应该是GTO中该行动的建议频率
        // 例如: GTO建议85% fold，则confidence=0.85（高信心）
        //      GTO建议50% raise/50% call，则confidence=0.50（中等信心）
        Double confidence = null;  // 使用null作为初始值，避免0.5的歧义
        boolean confidenceFromGTO = false;  // 标记是否从GTO获取
        
        // 优先使用GTO频率中选中行动的概率
        if (gtoFrequencies != null && !gtoFrequencies.isEmpty()) {
            String selectedAction = aiResponse.getAction();
            if (selectedAction != null) {
                // 获取该行动在GTO中的概率
                Double gtoProb = gtoFrequencies.get(selectedAction.toLowerCase());
                if (gtoProb != null) {
                    confidence = gtoProb;
                    confidenceFromGTO = true;
                    logger.debug("Confidence from GTO: {} for action: {}", confidence, selectedAction);
                }
            }
        }
        
        // 降级方案1: 如果没有从GTO获取，使用AI返回的equity
        if (!confidenceFromGTO && aiResponse.getHandStrength() != null 
                && aiResponse.getHandStrength().getEquity() != null) {
            confidence = aiResponse.getHandStrength().getEquity();
            logger.debug("Confidence from AI equity (fallback): {}", confidence);
        } 
        // 降级方案2: 使用数学计算的equity
        else if (!confidenceFromGTO && mathResult.equity > 0) {
            confidence = mathResult.equity;
            logger.debug("Confidence from math equity (fallback): {}", confidence);
        }
        
        // 最终降级：默认值
        if (confidence == null) {
            confidence = 0.5;
            logger.debug("Confidence using default: 0.5");
        }
        
        decision.setConfidence(confidence);
        
        // 推理过程（使用 AI 的 reasons 字段）
        String reasoning = aiResponse.getReasons();
        if (reasoning == null || reasoning.trim().isEmpty()) {
            reasoning = "AI 建议";
        }
        decision.setReasoning(reasoning);
        
        return decision;
    }
    
    /**
     * V4.9: 验证 AI 分析请求（hero_cards 必需）
     */
    private void validateAnalysisRequestV49(AIAnalysisRequest request) {
        if (request == null) {
            throw new IllegalArgumentException("Request cannot be null");
        }
        if (request.getTableId() == null || request.getTableId().trim().isEmpty()) {
            throw new IllegalArgumentException("Table ID is required (V4.9)");
        }
        if (request.getHandId() == null || request.getHandId().trim().isEmpty()) {
            throw new IllegalArgumentException("Hand ID is required (V4.9)");
        }
        if (request.getPlayerId() == null || request.getPlayerId().trim().isEmpty()) {
            throw new IllegalArgumentException("Player ID is required (V4.9)");
        }
        
        // V4.9: hero_cards 必需
        if (request.getHeroCards() == null || request.getHeroCards().isEmpty()) {
            throw new IllegalArgumentException("Hero cards are required for AI analysis (V4.9). " +
                "请求格式: {\"table_id\": \"...\", \"hand_id\": \"...\", \"player_id\": \"...\", " +
                "\"hero_cards\": [50, 47]}");
        }
        if (request.getHeroCards().size() != 2) {
            throw new IllegalArgumentException("Hero cards must contain exactly 2 cards, got: " + 
                request.getHeroCards().size() + " cards");
        }
    }
    
    /**
     * V4.9: 转换卡牌格式（数字 → 字符串）
     * 格式：11 → "Qd" (Q of diamonds)
     * 
     * 编码规则（与CardEncoder一致）：
     * 花色：方片(d)=0, 梅花(c)=13, 红桃(h)=26, 黑桃(s)=39
     * 点数：A=0, 2=1, 3=2, ..., 10=9, J=10, Q=11, K=12
     * 编码 = 花色基数 + 点数
     * 
     * 示例：
     * As = 39 + 0 = 39
     * Ks = 39 + 12 = 51
     * Qh = 26 + 11 = 37
     * Jd = 0 + 10 = 10
     */
    private List<String> convertCardsToStrings(List<Integer> cardNumbers) {
        if (cardNumbers == null || cardNumbers.isEmpty()) {
            return List.of();
        }
        
        // 卡牌编码：方片=0-12, 梅花=13-25, 红桃=26-38, 黑桃=39-51
        final String[] RANKS = {"A", "2", "3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K"};
        final String[] SUITS = {"d", "c", "h", "s"};  // 方片、梅花、红桃、黑桃
        
        return cardNumbers.stream()
            .map(cardNum -> {
                // 花色索引：0=方片, 1=梅花, 2=红桃, 3=黑桃
                int suitIndex = cardNum / 13;
                // 点数索引：0-12对应A-K
                int rankIndex = cardNum % 13;
                return RANKS[rankIndex] + SUITS[suitIndex];
            })
            .collect(java.util.stream.Collectors.toList());
    }
    
    /**
     * V4.9: 通过 table_id 和 hand_id 查找手牌
     */
    private PokerHand findHandByTableAndHandId(String tableId, String handId) {
        return pokerHandDao.findByHandIdAndTableId(handId, tableId).orElse(null);
    }
    
    
    /**
     * V4.9: 保存分析记录
     */
    private void saveAnalysisSnapshotV49(PokerHand hand, AIAnalysisRequest request, 
            AIAnalysisResponse response) {
        // TODO: 保存分析记录到数据库
        // V4.9: analysis_id 已从响应中移除，只在数据库中保存
        Long analysisId = generateAnalysisId();
        // response.setAnalysisId(analysisId);  // 已删除
    }
}